#include <iostream>
#include <stdio.h>
#include <sys/timeb.h>
#include <stdlib.h>
#include "util.h"
#include "L1_instruction_cache.h"
#include "L1_data_cache.h"
#include "L2_cache.h"

bool parseLine(char* line, int &instruction, unsigned int &addr)
{
    instruction = -1;
    if (sscanf(line,"#")){      // We are reading comments
        return true;            // return true
    }else{
        sscanf(line,"%d %x\n",&instruction,&addr);
        return false;
    }        
}

void initialize_system(L1_data_cache& data_Cache, L1_instruction_cache& inst_Cache, L2_cache& l2_Cache)
{
    data_Cache.l2 = &l2_Cache;              // connect l2_Cache --> l1 data cache
    l2_Cache.data_cache = &data_Cache;      // connect l1 data cache --> L2 cache
    inst_Cache.l2 = &l2_Cache;              // connect L1 instruction cache --> L2 cache 
    l2_Cache.inst_cache = &inst_Cache;      // connect L2 cache --> L1 instruction cache
    srand((unsigned)time(0));               // seeds the random function gen
}

void runTest(const char* filepath)
{
    L1_data_cache data_cache;  
    L1_instruction_cache inst_cache;
    L2_cache l2;
    
    initialize_system(data_cache, inst_cache, l2); // Connects L1 & L2 cache to each other
    
    FILE *fid = fopen(filepath, "r");

    if (fid == 0) {
        // cout << "Filepath: \"" << filepath << "\" does not exist\n";
        return;
    }
    
    unsigned int addr;
    int instruction;
    int i;
    char line[256];
    int inst = 0;
    int dat = 0;
    while (!feof(fid))
	{

        if (fgets(line, sizeof(line), fid) == 0)
            continue;

        bool comment = parseLine(line, instruction, addr);
        // printf("instruction %d\n",instruction);
        switch (instruction)
        {
            case 0:
                dat++;
            	data_cache.read(addr);
            	break;           	
            case 1:  
                dat++;              		
                data_cache.write(addr); //write data request from L1 cache
                break;
            case 2:		
                inst++;	          
                inst_cache.read(addr); //instruction fetch (treated like #0)
                break;		
    	    case 3:             //invalidate command from L2
        		l2.invalidateDataCache(addr);
        		break;
    	    case 8:            //clear the cache and reset all state
                //Question: Should we flush out the file id? or 
                //          just the line or what? 
                data_cache.ResetState();
                inst_cache.ResetState();
        		break;
    	    case 9:            //print contents and state of the cache
        		data_cache.PrintContents();
                inst_cache.PrintContents();
                break;
            default:
                if (comment){
                    FILE *fid = fopen(data_cache.filepath, "a");
                    if (fid){
                        fprintf(fid, "\n%s\n",line);
                        fclose(fid);
                    }		
                }
                break;
        }
    }

    long hits = data_cache.hits;
    long misses = data_cache.misses;
	long reads = data_cache.reads;
	long writes = data_cache.writes;
    float ratio = (float) hits/misses;
    float hitrate = (float)hits/(hits+misses)*100.;
    
    long inst_hits = inst_cache.hits;
    long inst_misses = inst_cache.misses;
	long inst_reads = inst_cache.reads;
	long inst_writes = inst_cache.writes;
    float inst_ratio = (float) inst_hits/inst_misses;
    float inst_hitrate = (float)inst_hits/(inst_hits+inst_misses)*100.;

    long total_hits = inst_hits + hits;
    long total_misses = inst_misses + misses;
    long total_reads = inst_reads + reads;
    long total_writes = inst_writes + writes;
    float total_ratio = (float) total_hits/total_misses;
    float total_hitrate = (float)total_hits/(total_hits+total_misses)*100.;
    
    printf("\n\n--------------------------------------\n");
    printf("L1 Data Cache - Statistics\n");
    printf("--------------------------------------\n");
    printf("total hits = %ld, misses = %ld, invalidates = %ld\n",hits,misses,data_cache.invalidates);
    printf("hit ratio = %3.3f, hit rate = %3.3f\n",ratio,hitrate);
	printf("total reads = %ld, total writes = %ld\n", reads, writes);
    printf("i = %d\n",i);

    printf("\n\n--------------------------------------\n");
    printf("L1 Instruction Cache - Statistics\n");
    printf("--------------------------------------\n");   
    printf("total hits = %ld, misses = %ld, invalidates = %ld\n",inst_hits,inst_misses,inst_cache.invalidates);
    printf("hit ratio = %3.3f, hit rate = %3.3f\n",inst_ratio,inst_hitrate);
	printf("total reads = %ld, total writes = %ld\n", inst_reads, inst_writes);
    printf("Total number data reads and writes = %d\nTotal number instruction fetches = %d\n",dat,inst);

    printf("\n\n--------------------------------------\n");
    printf("L1 Data + Instruction Cache - Statistics\n");
    printf("--------------------------------------\n");   
    printf("total hits = %ld, misses = %ld, invalidates = %ld\n",total_hits,total_misses,inst_cache.invalidates + data_cache.invalidates);
    printf("hit ratio = %3.3f, hit rate = %3.3f\n",total_ratio, total_hitrate);
    printf("total reads = %ld, total writes = %ld\n", total_reads, total_writes);
    printf("\n\nTotal number data reads and writes = %d\nTotal number instruction fetches = %d\n",dat,inst);


}

int main (int argc, const char * argv[])
{ 
   if (argc < 2){
       cout << "Enter trace filepath as cmd-line arg\n";
       cout << "e.g: \"<exec filename> <trace filepath>\"\n";
       return -1;
   } 
    runTest(argv[1]);  // Run test  
    return 0;
}

